﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading;
using Kugar.Core.ExtMethod;
using System.IO;


namespace Kugar.Core.Network.Tcp
{
    /// <summary>
    ///     TCP连接的处理类，包括处理TCP的连包问题等
    /// </summary>
    public class TcpClientEx
    {
        private bool isClosed = false;
        private Socket sc = null;// = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        private BufferManager bm = BufferManager.CreateBufferManager(100000, 40960);
        private byte[] receiveBuffer = null;
        private int receiveBufferPos = 0;
        private DataPacketHandle dataPacketHandle = new DataPacketHandle();
        private object lockerObj=new object();

        public TcpClientEx(Socket _socket):this(_socket,false,8192){}

        public TcpClientEx(Socket _socket, bool isOwner) : this(_socket, isOwner, 8192) { }

        /// <summary>
        ///     
        /// </summary>
        /// <param name="_socket"></param>
        /// <param name="isOwner">该socket是否为私有的,即当当前类关闭之后,是否一起关闭socket连接</param>
        /// <param name="maxPacketLength">最大的一个数据包大小</param>
        public TcpClientEx(Socket _socket,bool isOwner,int maxPacketLength)
        {
            if (maxPacketLength<1024)
            {
                throw new ArgumentOutOfRangeException(@"maxPacketLength", "最大数据包大小必须>=1024");
            }

            receiveBuffer=new byte[(int)(maxPacketLength*1.5)];

            sc = _socket;
            sc.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, OnReceiveCallback, null);

            IsOwner = isOwner;

            dataPacketHandle.BufferManage = bm;

        }

        public bool IsOwner { protected set; get; }

        /// <summary>
        ///     设置用于分割不同分包的包头数据，默认为： 0x5A 0xA5
        /// </summary>
        public byte[] HeaderData
        {
            get
            {
                return dataPacketHandle.HeaderData;
            }
            set { dataPacketHandle.HeaderData = value; }
        }

        /// <summary>
        ///     是否启用连包处理
        /// </summary>
        public bool EnableSpliteBag
        {
            get
            {
                return dataPacketHandle.EnableSpliteBag;
            }
            set { dataPacketHandle.EnableSpliteBag = value; }
        }

        /// <summary>
        ///     是否启用数据压缩（只压缩数据部分），如果启用，则接收方也应该同时启用该属性
        /// </summary>
        public bool EnableCompress
        {
            get
            {
                return dataPacketHandle.EnableCompress;
            }
            set { dataPacketHandle.EnableCompress = value; }
        }

        public void Send(byte[] data)
        {
            Send(data, 0, data.Length);
        }

        public void Send(ArraySegment<byte> data )
        {
            this.Send(data.Array,data.Offset,data.Count);
        }

        /// <summary>
        ///     发送数据
        /// </summary>
        /// <param name="data">发送的数据缓冲区</param>
        /// <param name="startIndex">发送的起始位置</param>
        /// <param name="length">发送的数据长度</param>
        public void Send(byte[] data, int startIndex, int length)
        {
            if (isClosed || !sc.Connected)
            {
                return;
            }

            if (!data.IsInEnableRange(startIndex, length))
            {
                return;
            }

            byte[] newData = dataPacketHandle.PacketData(data, startIndex, length);

            if (newData != null && newData.Length > 0)
            {
                try
                {
                    if (isClosed || !sc.Connected)
                    {
                        return;
                    }

                    var au = new AutoResetEvent(false);
                    sc.BeginSend(newData, 0, newData.Length, SocketFlags.None, (s) =>
                                                                               {
                                                                                   bm.ReturnBuffer(newData);
                                                                                   au.Set();
                                                                               }, null);


                    au.WaitOne(5000);

                    //sc.Send(newData, 0,newData.Length, SocketFlags.None);
                }
                catch (Exception ex)
                {
                    Events.EventHelper.Raise(ExceptionHandle,this,new ExceptionHandleEventArgs(ex));
                }
                
            }


        }

        public void Close()
        {
            if (IsOwner && !isClosed)
            {
                lock (lockerObj)
                {
                    if (!isClosed)
                    {
                        try
                        {
                            sc.Shutdown(SocketShutdown.Both);
                        }
                        catch (Exception)
                        {

                        }

                        try
                        {
                            if(sc.Connected)
                            {
                                try
                                {
                                    sc.Disconnect(false);
                                }
                                catch (Exception)
                                {
                        
                                }

                            }

                            sc.Close();
                        }
                        catch (Exception)
                        {

                        }

                    }
                }

                isClosed = true;
                bm.Clear();
                //sc.Dispose();
            }

            
        }

        private void OnReceiveCallback(IAsyncResult ar)
        {
            int revCount = 0;

            try
            {
                revCount = sc.EndReceive(ar);
            }
            catch 
            {
                return;
            }

            if (revCount > 0)
            {
                var retList = new List<ArraySegment<byte>>();

                int tempPos = 0;

                Interlocked.Exchange(ref tempPos, receiveBufferPos);

                lock (lockerObj)
                {
                    var pos = dataPacketHandle.SplitPacket(receiveBuffer, tempPos, revCount, retList);

                    if (pos > tempPos)
                    {
                        var moveCount = tempPos + revCount - pos;

                        if (moveCount > 0)
                        {
                            receiveBuffer.MoveItems(pos, moveCount, 0);

                            Interlocked.Exchange(ref receiveBufferPos, moveCount);
                        }
                    }
                }

                if (retList.Count > 0)
                {
                    ThreadPool.QueueUserWorkItem(OnDataReceive, retList);
                }

            }



            if (!isClosed && revCount!=0)
            {
                try
                {
                    sc.BeginReceive(receiveBuffer, receiveBufferPos, receiveBuffer.Length - receiveBufferPos, SocketFlags.None,
                                    OnReceiveCallback, null);          
                }
                catch (Exception)
                {
                    Close();
                    //throw;
                }
      
            }


        }

        private void OnDataReceive(object state)
        {

            if (DataReceive != null)
            {
                var databuff = (List<ArraySegment<byte>>)state;

                if (databuff != null && databuff.Count > 0)
                {
                    for (int i = 0; i < databuff.Count; i++)
                    {
                        try
                        {
                            DataReceive(this, new DataReceiveEventArgs(databuff[i],this));
                        }
                        catch (Exception)
                        {
                            continue;
                        }
                        finally
                        {
                            bm.ReturnBuffer(databuff[i].Array);
                        }
                    }
                }
            }


        }

        public event EventHandler<DataReceiveEventArgs> DataReceive;

        public event EventHandler<ExceptionHandleEventArgs> ExceptionHandle;
    }

    /// <summary>
    ///     <para>
    ///         处理数据的封包以及分包
    ///     </para>
    ///     
    ///         可能会出现的问题:<br/>
    ///         因为该类本身只负责分包以及封包的功能,不存在对数据包的缓存,所以,如果接收的数据包过大,很可能会出现无法正常分包,
    ///         比如:要接收的数据包的大小为10K,当前的接收缓冲区之后8K,根据算法,分包函数会返回0,表示需要等待数据包接收完毕,
    ///         此时则需要加大buff的缓存区大小
    ///             
    /// </summary>
    /// <remarks>
    ///     数据包结构:<br/>`
    ///             数据包起始标识(5AA5) + 数据区长度(int) + 实际数据区 + CRC校验码<br/>
    /// 
    ///     分包实现算法:<br/>
    ///         1.找到第一个与HeaderData[0]相同的数据,如果该数据包包头不完整,并且已经达到buff的末尾时,返回当前的指针<br/>
    ///         如果未达到末尾,则往后继续寻找完整的数据包头信息,并读取数据区长度<br/>
    ///         2.如果寻找到完整的数据包包头信息,则检查剩余的缓冲区内的数据是否足够的数据,如果数据不足,则返回当前的指针位置
    ///         如果数据长度足够,则从缓冲区中申请一块指定大小的缓存,然后新建一个ArraySegment类,存放指定的缓存区,并复制数据
    ///         到该区域,最后重新计算实际数据区的偏移和大小
    ///         3.如果未启用分包的话,则直接将数据包打包为ArraySegment类返回
    ///         
    /// </remarks>
    public class DataPacketHandle
    {
        private const int CRCLength = 2;    //CRC校验码长度,,4为32位CRC校验,2为16位CRC
        private const int DataCountFieldLength = 4;//存放数据去长度的变量长度,,,32位的数据

        private readonly static byte[] defaultHeaderData = new byte[] { 0x5A, 0xA5 };  //默认的数据包包头为： 5A A5
        private byte[] _headerData = null;
        private int _headerDataLength = 0;

        public DataPacketHandle() : this(null) { }

        public DataPacketHandle(BufferManager bufferManager)
        {

            BufferManage = bufferManager;

            _headerData = new byte[defaultHeaderData.Length];

            defaultHeaderData.CopyTo(_headerData, 0);

            EnableSpliteBag = true;
            EnableCompress = false;
        }

        public BufferManager BufferManage { set; get; } //缓冲区

        /// <summary>
        ///     设置用于分割不同分包的包头数据，默认为： 0x5A 0xA5
        /// </summary>
        public byte[] HeaderData
        {
            get
            {
                return _headerData;
            }
            set
            {
                if (value == null || value.Length <= 0 || value == _headerData)
                {
                    throw new ArgumentOutOfRangeException("value");
                }

                bool isSame = true;

                if (value.Length != _headerData.Length)
                {
                    isSame = false;
                }
                else
                {
                    for (int i = 0; i < value.Length; i++)
                    {
                        if (value[i] != _headerData[i])
                        {
                            isSame = false;
                        }
                    }
                }

                if (!isSame)
                {
                    _headerData = new byte[value.Length];
                    value.CopyTo(_headerData, 0);
                    _headerDataLength = value.Length;
                    //_headerData = value;
                }

            }
        }

        /// <summary>
        ///     是否启用连包处理
        /// </summary>
        public bool EnableSpliteBag { set; get; }

        /// <summary>
        ///     是否启用数据压缩（只压缩数据部分），如果启用，则接收方也应该同时启用该属性
        /// </summary>
        public bool EnableCompress { set; get; }

        /// <summary>
        ///     将数据进行分包处理,分包后的结果存放在dataList参数中
        ///     注： 如果输入缓冲区中，包含未接收完成的数据包段，则函数返回最后一个未接收完成的数据包的包头起始索引；
        ///             如果输入缓冲区的数据为无效数据，则函数返回offset；
        ///             如果输入缓冲区的数据全部都为完整的数据包，则返回offset。
        /// </summary>
        /// <param name="buff">输入缓冲区</param>
        /// <param name="offset">缓冲区偏移量</param>
        /// <param name="count">缓冲区中有效数据长度</param>
        /// <param name="dataList">用于存放分包后的数据包列表</param>
        /// <returns></returns>
        public int SplitPacket(byte[] buff, int offset, int count, List<ArraySegment<byte>> dataList)
        {
            if (!EnableCompress && !EnableSpliteBag)    //如果不需要处理分包和压缩的话,直接返回
            {
                dataList.Add(new ArraySegment<byte>(buff, offset, count));
                return 0;
            }

            ArraySegmentEx<byte>currentDataBlock = null;
            var currentPos = offset;
            
            //预先计算好常用的值，省了每次都要去计算
            
            var srcBufferMaxPos = offset + count;   //原缓存区中，最大的偏移值
            var packetHeaderLength = HeaderData.Length + DataCountFieldLength;  //包头的总长度
            var packetTotalInfoLength = packetHeaderLength + CRCLength; //数据包中，总共用于描述的信息长度，包头+CRC校验
            

            while (currentPos < srcBufferMaxPos)
            {
                var newPos = GetNextPacketStartIndex(buff, currentPos, srcBufferMaxPos); //查找下一个包含包头数据的起始索引

                if (newPos == -1)   //如果剩下的数据中不存在指定的数据包包头信息则退出循环处理
                {
                    currentPos = 0;
                    break;
                }

                if (newPos + packetHeaderLength > srcBufferMaxPos)  //如果数据包头部不完整,则留待下次继续处理
                {
                    break;
                }

                var dataCount = BitConverter.ToInt32(buff, newPos + HeaderData.Length);  //读取数据区长度标识

                if (dataCount <= 0)
                {
                    //当读取出来的数据长度小于0时，当前指针加1，继续循环
                    currentPos++;
                    continue;
                }

                byte[] block = null;

                //缓冲区的大小： 数据区长度+4个字节的int（数据区长度标识）+包头标识长度+CRC校验码长度
                int requestBlockSize = dataCount + packetTotalInfoLength;

                if (BufferManage != null)
                {
                    //向缓存管理类申请指定空间大小的缓冲区，
                    block = BufferManage.TakeBuffer(requestBlockSize);
                }
                else
                {
                    block = new byte[requestBlockSize];
                }

                //新建一个对象
                currentDataBlock = new ArraySegmentEx<byte>(block, 0, requestBlockSize);

                //将当前指针移动到新的包头数据前
                currentPos = newPos;

                //如果当前缓冲区中剩余的数据量不足,则不进行处理,留待下次,并返回当前的指针位置
                if (buff.Length - currentPos - currentDataBlock.Count < 0)
                {
                    break;
                }

                //计算可以复制的数据数量，（当前剩余需要复制的数量与当前输入数据区中剩余数量，两个值中取小值）
                var copyCount = Math.Min(currentDataBlock.Count - currentDataBlock.Offset, count);

                //数组复制
                Array.Copy(buff, currentPos , currentDataBlock.Array, currentDataBlock.Offset, copyCount);

                currentDataBlock.Offset += packetHeaderLength;  //计算实际数据区的起始位置，跳过数据包包头与数据长度区
                currentDataBlock.Count -= packetTotalInfoLength; //重新计算数据长度，减去包头与CRC校验的长度

                //对数据进行CRC校验
                if (currentDataBlock.Array.GetCRCCode(currentDataBlock.Offset, currentDataBlock.Count+ CRCLength ) == 0) //校验CRC
                {
                    var totalCount = currentDataBlock.Count;

                    if (EnableCompress) //如果启用了压缩,,则解压缩数据区
                    {
                        totalCount = currentDataBlock.Array.UnCompressToByteArray(currentDataBlock.Offset,
                                                                                  totalCount,
                                                                                  currentDataBlock.Array,
                                                                                  currentDataBlock.Offset);
                    }

                    currentDataBlock.Count = totalCount;

                    dataList.Add(currentDataBlock); //放入结果集中

                    currentPos += copyCount;    //移动总复制进度的指针到下个位置
                }
                else //如果校验错误，则回收之前申请的数据缓冲区
                {
                    if (BufferManage != null)
                    {
                        currentPos ++;
                        BufferManage.ReturnBuffer(currentDataBlock.Array);
                    }
                }

                

                //}
            }

            //if (moveItemIndex > 0 && moveCount > 0)
            //{
            //    buff.MoveItems(moveItemIndex, moveCount, 0);
            //}

            return currentPos;
        }

        /// <summary>
        ///     封包数据,如添加分包包头信息,压缩数据等
        /// </summary>
        /// <param name="buff">输入缓冲区</param>
        /// <param name="offset">缓冲区偏移量</param>
        /// <param name="count">缓冲区中有效数据长度</param>
        /// <returns>返回封包后的数据</returns>
        public byte[] PacketData(byte[] buff, int offset, int count)
        {
            

            using (var ms = new MemoryStream(count + HeaderData.Length + DataCountFieldLength + CRCLength))
            {
                if (EnableCompress)
                {
                    buff = buff.CompressToByteArray();
                    count = buff.Length;
                    offset = 0;
                }

                //var newbuff = BufferManage.TakeBuffer(HeaderData.Length + 4 + count+ 2);

                if (EnableSpliteBag)
                {
                    //Array.Copy(_headerData,0,newbuff,0,_headerDataLength);
                    ms.Write(HeaderData, 0, HeaderData.Length);

                    //Array.Copy(BitConverter.GetBytes(count), 0, newbuff, _headerDataLength,4);
                    ms.Write(BitConverter.GetBytes(count), 0, 4);

                    //Array.Copy(buff, 0, newbuff, _headerDataLength+4,buff.Length);
                    ms.Write(buff, offset, count);

                    //Array.Copy(BitConverter.GetBytes(buff.GetCRCCode(offset, count)), 0, newbuff, _headerDataLength + 4 + buff.Length, 2);
                    ms.Write(BitConverter.GetBytes(buff.GetCRCCode(offset, count)), 0, 2);
                }
                else
                {
                    ms.Write(buff, offset, count);
                }

                //BufferManage.ReturnBuffer(newbuff);

                ms.Seek(0, SeekOrigin.Begin);

                var newData = ms.ReadAllBytes();

                return newData;
            }
        }

        private int GetNextPacketStartIndex(byte[] buff, int offset,int endOffset)
        {
            var currentPos = offset;
            var newPos = -1;
            var headerFirst = HeaderData[0];
            //var lastOffset = offset + count;

            while (currentPos < endOffset)
            {
                newPos = Array.FindIndex(buff, currentPos, (s) => s == headerFirst);

                if (newPos < 0)
                {
                    break;
                }

                var checkLength = Math.Min(HeaderData.Length, buff.Length - 1 - newPos);

                var isSame = true;

                for (int i = 0; i < checkLength; i++)
                {
                    if (buff[newPos + i] != HeaderData[i])
                    {
                        isSame = false;
                        continue;
                    }
                }

                if (isSame)
                {
                    break;
                }
                else
                {
                    currentPos = newPos + 1;
                }
            }

            return newPos;

        }
    }

    public class DataReceiveEventArgs : EventArgs
    {
        public DataReceiveEventArgs(ArraySegment<byte> data,TcpClientEx srcTcp)
        {
            Data = data;
            SrcTcp = srcTcp;
        }

        public TcpClientEx SrcTcp { private set; get; }

        public ArraySegment<byte> Data { get; private set; }
    }

    public class ExceptionHandleEventArgs:EventArgs
    {
        public ExceptionHandleEventArgs(Exception error)
        {
            Error = error;
            IsHandled = false;
        }

        public Exception Error { protected set; get; }
        public bool IsHandled;
    }

    public class ArraySegmentEx<T>
    {
        public ArraySegmentEx(T[] array, int offset, int count)
        {
            this.Array = array;
            Offset = offset;
            Count = count;
        }

        public int Offset { set; get; }

        public int Count { set; get; }

        public T[] Array { get; private set; }

        public static implicit operator ArraySegment<T>(ArraySegmentEx<T> s)
        {
            return new ArraySegment<T>(s.Array, s.Offset, s.Count);
        }
    }

}


